package com.esdk.sql.orm;

import cn.hutool.json.JSONUtil;
import cn.hutool.json.XML;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.esdk.esdk;
import com.esdk.utils.*;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Map.Entry;

/***
 * @author 范飞宇
 * @since 2006.?.? 
 */
public class ABRow<T extends ABRow> implements IRow, Comparable<T>{
  private Map<String,Object> record;
  private transient HashMap mapChanged;
  protected transient boolean isChanged;
	protected String[] columns;
  
  public ABRow(Map<String,Object> map) {
    this.record=map;
  }
  
  public ABRow(ABRow abrow) {
    this.record=abrow.record;
  }
  
  public ABRow(IRow row) {
  	record=row.record();
  }
  
  public ABRow(String[] columns){
		this.columns=columns;
  	record=new HashMap();
  	for(int i=0;i<columns.length;i++){
			straightSet(columns[i],null);
		}
	}
  
	public ABRow(String[] columns,Object[] values){
		this.columns=columns;
		record=new HashMap();
		for(int i=0;i<columns.length;i++){
			Object value=(i>=values.length)?"":values[i];
			straightSet(columns[i],value);
		}
	}

	private Map getMapChanged() {
    if(mapChanged==null)
      mapChanged=new HashMap();
    return mapChanged;
  }
  
  @JSONField(serialize=false) 
  public String[] getColumns(){
		return this.columns!=null?columns:esdk.array.toArray(record.keySet());
  }

  public boolean hasColumnName(String name){
    return record.containsKey(name);
  }

	public T load(Map datas){
		if(datas==null)
			return (T)this;
		for(Iterator iter=record.keySet().iterator();iter.hasNext();){
			String key=(String)iter.next();
			if(datas.containsKey(key))
				straightSet(key,datas.get(key));
		}
		return (T)this;
	}

	public T removeColumns(String... cols){
		getColumns();
		columns=esdk.array.remove(columns,cols);
		for(String col:cols){
			record.remove(col);
		}
		return (T)this;
	}

	public T load(Map map,boolean ignoreNull,boolean checkExistColumn,boolean isFormatJavaBeanProperty){
		for(Iterator iter=map.entrySet().iterator();iter.hasNext();){
			Entry entry=(Entry)iter.next();
			String columnName=(String)entry.getKey();
			boolean foundColumn=false;
			if(this.hasColumnName(columnName))
				foundColumn=true;
			else if(isFormatJavaBeanProperty){
				columnName=esdk.str.toUnderlineCase((String)entry.getKey());
				if(this.hasColumnName(columnName))
					foundColumn=true;
				else{
					columnName=(String)entry.getKey();
				}
			}
			if(!checkExistColumn||foundColumn)
				if(!ignoreNull||entry.getValue()!=null)
					this.put(columnName,entry.getValue());

		}
		return (T)this;
	}
  
  private void straightSet(String key,Object v){
    record.put(key,v);
  }

  private boolean isSameValue(String key,Object newvalue){
    Object oldvalue=record==null?null:get(key);
    if(newvalue==oldvalue)return true;//if(null==null) return true;
    if(newvalue instanceof Date && oldvalue instanceof Date)
    	return ((Date)newvalue).getTime()==((Date)oldvalue).getTime();
    return oldvalue!=null&&oldvalue.equals(newvalue);
  }
  
  public T put(String key,Object v){
    if(!isSameValue(key,v)) {
      setNewValue(key,v);
    }
		return (T)this;
  }
  
  private void setNewValue(String key,Object v){
    straightSet(key,v);
    isChanged=true;
    getMapChanged().put(key,v);
  }
  
  public Object get(String key){
    /*return record.get(key);*/
    return esdk.str.get(record,key);
  }

  @JSONField(serialize=false) 
  public boolean isChanged() {
    return isChanged;
  }
  
  @JSONField(serialize=false) 
  public Map getChanged() {
    return mapChanged;
  }

  public Object[] toArray() {
  	return RowUtils.toArray(this);
  }

  public Object[] toArray(String...labels) {
  	return RowUtils.toArray(this,labels);
  }

	public List toList() {
		return RowUtils.toList(this);
	}

	public List toList(String...labels) {
		return RowUtils.toList(this,labels);
	}

  @Override
  public String toXml() {
  	return XML.toXml(JSONUtil.parseObj(toMap(true)),Constant.RowXmlIdentifier);
  }
  
  @Override
  public String toXml(String... labels) {
  	return XML.toXml(JSONUtil.parseObj(toMap(true,labels)),Constant.RowXmlIdentifier);
  }

	@Override
  public String toCsv(){
    return toCsv(true);
  }
	@Override
	public String toCsv(boolean isExportHeaders){
		return toCsv(isExportHeaders,record.keySet().toArray(new String[0]));
	}
	@Override
  public String toCsv(String... labels){
    return toCsv(true,labels);
  }
	@Override
	public String toCsv(boolean isExportHeaders,String... labels){
		TString result=new TString();
		if(isExportHeaders)
			result.append(new CharAppender(',').add(labels));
		CharAppender ca=new CharAppender(',');
		for(int j=0;j<labels.length;j++){
			ca.append(EasyCsv.csvEncode(esdk.str.getStringNoNull(this.get(labels[j]))));
		}
		if(isExportHeaders&&ca.length()>0)
			result.appendNewLine();
		result.append(ca);
		return result.toString();
	}

  public String toString(){
  	return JsonUtils.toJSONString(toJsonObject(),JsonUtils.getSerializeConfig());
  }
  
  /**默认输出驼峰格式*/
	@Override public JSONObject toJsonObject() {
  	return toJsonObject(true);
  }

  public JSONObject toJsonObject(boolean isFormatJavaBeanName) {
  	if(isFormatJavaBeanName) {
			JSONObject result=new JSONObject(true);
    	String[] columns=esdk.str.distinct(getColumns(),esdk.str.toArray(this.record.keySet()));
    	for(int i=0;i<columns.length;i++){
      	result.put(isFormatJavaBeanName?esdk.str.toCamelCase(columns[i]):columns[i],this.record.get(columns[i]));
  		}
  		return result;
  	}
  	else
  		return JsonUtils.toJsonObject(this.record);
  }

  public JSONObject toJsonObject(String... columns) {
		JSONObject result=new JSONObject(true);
  	for(int i=0;i<columns.length;i++){
    	result.put(columns[i],this.record.get(columns[i]));
		}
  	return result;
  }

  public JSONObject toJsonObject(boolean isFormatJavaBeanName,String... columns) {
  	if(columns.length==0)
  		columns=esdk.str.toArray(this.record.keySet());
  	if(isFormatJavaBeanName) {
			JSONObject result=new JSONObject(true);
    	for(int i=0;i<columns.length;i++){
      	result.put(isFormatJavaBeanName?esdk.str.toCamelCase(columns[i]):columns[i],this.record.get(columns[i]));
  		}
  		return result;
  	}
  	else
  		return toJsonObject(columns);
  }
  
  public Map toMap() {
  	return toMap(false);
  }

	public Map toMap(boolean isFormatJavaBeanName,String...columns) {
		return toMap(isFormatJavaBeanName,columns,esdk.array.EmptyStrArr);
	}
  public Map toMap(boolean isFormatJavaBeanName,String[] columns,String[] excludeCols) {
  	LinkedHashMap result=new LinkedHashMap(this.record.size());
  	for(String key:columns) {
  		if((columns.length==0||esdk.str.existOf(columns,key))&&!esdk.str.existOf(excludeCols,key)) {
	  		Object value=record.get(key);
	  		if(isFormatJavaBeanName)
	  			key=esdk.str.toCamelCase(key,false);
	  		result.put(key,value);
  		}
  	}
  	return result;
  }
  
  public T clone() {
    ABRow result=new ABRow(this.columns);
    result.load(this);
    return (T)result;
  }
  
  @JSONField(serialize=false) 
  public Map record() {
    return record;
  }
  
  public T load(IRow row){
		if(row==null)
			return (T)this;
		for(Iterator iter=record.keySet().iterator();iter.hasNext();){
			String key=(String)iter.next();
			record.put(key,row.get(key));
		}
		return (T)this;
	}
  
  public void load(IRow row,boolean isNullable){
    if(row==null)return ;
    try{
      for(Iterator iter=record.keySet().iterator();iter.hasNext();){
        String key=(String)iter.next();
        Object newValue=row.get(key);
        if(isNullable||newValue!=null)
        	record.put(key,newValue);
      }
    }
    catch(Exception e){
      throw new RuntimeException(e);
    }
  }

  public T load(Object bean){
    if(bean!=null)
    	RowUtils.loadFrom(bean,this,false);
    return (T)this;
  }

  public T load(Object bean,boolean isNullable){
    if(bean!=null)
    	RowUtils.loadFrom(bean,this,isNullable);
    return (T)this;
  }

  public Object copy(Object bean) {
  	return RowUtils.copyTo(this,bean,false);
  }
  
  public Object copy(Object bean,boolean isNullable) {
  	return RowUtils.copyTo(this,bean,isNullable);
  }

	@Override public Boolean getBoolean(String key){
		return EasyObj.convert(get(key),Boolean.class);
	}

	@Override public Double getDouble(String key){
		return EasyObj.convert(get(key),Double.class);
	}

	@Override public Float getFloat(String key){
		return EasyObj.convert(get(key),Float.class);
	}

	@Override public Integer getInteger(String key){
	   return EasyObj.convert(get(key),Integer.class);
	}

	@Override public Long getLong(String key){
		return EasyObj.convert(get(key),Long.class);
	}

	@Override public Short getShort(String key){
		return EasyObj.convert(get(key),Short.class);
	}

	@Override public String getString(String key){
		return EasyObj.convert(get(key),String.class);
	}
	@Override public Date getDate(String key){
		return EasyObj.convert(get(key),Date.class);
	}

	@Override
	public LocalDateTime getLocalDateTime(String key){
		return EasyObj.convert(get(key),LocalDateTime.class);
	}
	@Override public BigDecimal getBigDecimal(String key){
		return EasyObj.convert(get(key),BigDecimal.class);
	}

	@Override
	public int compareTo(T obj){
		return new ComparableArray(this.toArray()).compareTo(new ComparableArray(((IRow)obj).toArray()));
	}

	@Override
	public boolean equals(Object o){
		return RowUtils.equals(this,(IRow)o);
	}

}